<?php

defined('WEKIT_VERSION') || exit('Forbidden');

/**
 * IP地址查询.
 *
 * @author jinlong.panjl <jinlong.panjl@aliyun-inc.com>
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.phpwind.com
 *
 * @version $Id$
 */
class PwIptable
{
    private $_from;
    private $_dataFile;

    /**
     * @param $dirName
     *
     * @return unknown_type
     */
    public function __construct($dirName = null)
    {
        $this->_dirName = $dirName ? $dirName : Wind::getRealDir('REP:ipdata');
        $this->_dataFile = $this->_dirName.'/ipindex.dat';
        $this->_wryFile = $this->_dirName.'/wry.dat';
        $this->_from = file_exists($this->_wryFile) ? 'wry' : 'index';
    }

    /**
     * 根据域名地址获取IP所属地.
     *
     * @param string $domain
     *
     * @return string
     */
    public function getIpFromByDomain($domain)
    {
        if (!$domain) {
            return 'Unknown';
        }
        $ip = gethostbyname($domain);

        return $this->getIpFrom($ip);
    }

    /**
     * 根据IP地址获取IP所属地.
     *
     * @param string $ip
     *
     * @return string
     */
    public function getIpFrom($ip)
    {
        if (!$ip || !$this->_isCorrectIpAddress($ip)) {
            return 'Unknown';
        }
        $action = sprintf('_getIpFrom%s', ucfirst($this->_from));
        if (!method_exists($this, $action)) {
            return 'Unknown';
        }

        return $this->$action($ip);
    }

    /**
     * 判断是否是正确的IP地址
     *
     * @param string $ip
     *
     * @return bool
     */
    private function _isCorrectIpAddress($ip)
    {
        return preg_match('/^((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|(\d))(\.((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|(\d))){3}$/', $ip);
    }

    /**
     * 从ip索引文件获取.
     *
     * @param string $ip
     *
     * @return string
     */
    private function _getIpFromIndex($ip)
    {
        $IpTable = new IpTableIndex();

        return $IpTable->getIpFromIndex($ip);
    }

    /**
     * 从ip纯真库获取.
     *
     * @param string $ip
     *
     * @return string
     */
    private function _getIpFromWry($ip)
    {
        $IpTable = new IpTableWry();

        return $IpTable->getIpFromWry($ip);
    }
}

/**
 * 根据二分法从ip纯真库查找.
 *
 * @param $fp
 * @param $firstip
 * @param $lastip
 *
 * @return $totalip
 */
class IpTableWry
{
    private $_fp;
    private $_firstip;
    private $_lastip;
    private $_totalip;

    public function __construct($fileName = null)
    {
        $this->_fp = 0;
        $fileName = $fileName ? $fileName : Wind::getRealDir('REP:ipdata').'/wry.dat';
        if (($this->_fp = fopen($fileName, 'rb')) !== false) {
            $this->_firstip = $this->getlong();
            $this->_lastip = $this->getlong();
            $this->_totalip = ($this->_lastip - $this->_firstip) / 7;
        }
    }

    /**
     * 根据所给 IP 地址或域名返回所在地区信息.
     *
     * @param string $ip
     *
     * @return string
     */
    public function getIpFromWry($ip)
    {
        $unknowIp = 'Unknown';
        if (!$this->_fp) {
            return $unknowIp;
        }
        $ip = $this->packip($ip);
        // 二分法搜索索引区间
        $l = 0;
        $u = $this->_totalip;
        $findip = $this->_lastip;
        while ($l <= $u) {
            $i = floor(($l + $u) / 2);
            fseek($this->_fp, $this->_firstip + $i * 7);
            $beginip = strrev(fread($this->_fp, 4));
            if ($ip < $beginip) {
                $u = $i - 1;
            } else {
                fseek($this->_fp, $this->getlong3());
                $endip = strrev(fread($this->_fp, 4));
                if ($ip > $endip) {
                    $l = $i + 1;
                } else {
                    $findip = $this->_firstip + $i * 7;
                    break;
                }
            }
        }

        //获取查找到的IP地址
        fseek($this->_fp, $findip);
        $location['beginip'] = long2ip($this->getlong());
        $offset = $this->getlong3();
        fseek($this->_fp, $offset);
        $location['endip'] = long2ip($this->getlong());
        $byte = fread($this->_fp, 1);
        switch (ord($byte)) {
            case 1:
                $countryOffset = $this->getlong3();
                fseek($this->_fp, $countryOffset);
                $byte = fread($this->_fp, 1);
                switch (ord($byte)) {
                    case 2:
                        fseek($this->_fp, $this->getlong3());
                        $country = $this->getstring();
                        fseek($this->_fp, $countryOffset + 4);
                        $area = $this->getarea();
                        break;
                    default:
                        $country = $this->getstring($byte);
                        $area = $this->getarea();
                        break;
                }
                break;
            case 2:
                fseek($this->_fp, $this->getlong3());
                $country = $this->getstring();
                fseek($this->_fp, $offset + 8);
                $area = $this->getarea();
                break;
            default:
                $country = $this->getstring($byte);
                $area = $this->getarea();
                break;
        }
        if ($country == ' CZ88.NET') {
            $country = $unknowIp;
        }
        if ($area == ' CZ88.NET') {
            $area = '';
        }

        return Pw::convert($country.$area, 'GBK');
    }

    /**
     * 返回读取的长整型数.
     *
     * @return int
     */
    private function getlong()
    {
        $result = unpack('Vlong', fread($this->_fp, 4));

        return $result['long'];
    }

    /**
     * 返回读取的3个字节的长整型数.
     *
     * @return int
     */
    private function getlong3()
    {
        $result = unpack('Vlong', fread($this->_fp, 3).chr(0));

        return $result['long'];
    }

    /**
     * 返回压缩后可进行比较的IP地址
     *
     * @param string $ip
     *
     * @return string
     */
    private function packip($ip)
    {
        return pack('N', intval(ip2long($ip)));
    }

    /**
     * 返回读取的字符串.
     *
     * @param string $data
     *
     * @return string
     */
    private function getstring($data = '')
    {
        $char = fread($this->_fp, 1);
        while (ord($char) > 0) {
            $data .= $char;
            $char = fread($this->_fp, 1);
        }

        return $data;
    }

    /**
     * 返回地区信息.
     *
     * @return string
     */
    private function getarea()
    {
        $byte = fread($this->_fp, 1);
        switch (ord($byte)) {
            case 0:
                $area = '';
                break;
            case 1:
                fseek($this->_fp, $this->getlong3());
                $area = $this->getstring();
                break;
            case 2:
                fseek($this->_fp, $this->getlong3());
                $area = $this->getstring();
                break;
            default:
                $area = $this->getstring($byte);
                break;
        }

        return $area;
    }
}

/**
 * 根据索引文件从文件里查找.
 *
 * @param $_bsize
 * @param $_dirName
 * @param $_indexFile
 *
 * @return string
 */
class IpTableIndex
{
    private $_bsize;
    private $_dirName;
    private $_indexFile;

    /**
     * @param $dirName
     *
     * @return unknown_type
     */
    public function __construct($fileName = null)
    {
        $this->_bsize = 1024;
        $this->_dirName = $fileName ? dirname($fileName) : Wind::getRealDir('REP:ipdata');
        $this->_indexFile = $this->_dirName.'/ipindex.dat';
    }

    /**
     * 通过ipindex从文本获取IP信息.
     *
     * @param $ip
     *
     * @return string
     */
    public function getIpFromIndex($ip)
    {
        $unknowIp = 'Unknown';
        $d_ip = explode('.', $ip);
        $txt = $this->_dirName.'/'.$d_ip[0].'.txt';
        $tag_1 = $d_ip[0];
        $tag_2 = $d_ip[1];
        if (!file_exists($txt)) {
            $tag_1 = 0;
            $tag_2 = $d_ip[0];
            $txt = $this->_dirName.'/'.'0.txt';
        } else {
            $d_ip[0] = $d_ip[1];
            $d_ip[1] = $d_ip[2];
            $d_ip[2] = $d_ip[3];
            $d_ip[3] = '';
        }
        $ipIndex = $this->_getIPIndex($tag_1, $tag_2);
        if (empty($ipIndex)) {
            return $unknowIp;
        } elseif ($ipIndex[0] == -1) {
            $offset = 0;
            $offsize = filesize($txt);
        } else {
            $offset = $ipIndex[0];
            $offsize = $ipIndex[1] - $ipIndex[0];
        }
        if ($offsize < 1) {
            return $unknowIp;
        }
        if ($handle = @fopen($txt, 'rb')) {
            flock($handle, LOCK_SH);
            fseek($handle, $offset, SEEK_SET);
            $d = "\n".fread($handle, $offsize);
            $d .= fgets($handle, 100);
            $wholeIP = $d_ip[0].'.'.$d_ip[1].'.'.$d_ip[2];
            $d_ip[3] && $wholeIP .= '.'.$d_ip[3];
            $wholeIP = str_replace('255', '*', $wholeIP);
            $f = $l_d = 0;
            if (($s = strpos($d, "\n$wholeIP\t")) !== false) {
                $s = $s + $offset;
                fseek($handle, $s, SEEK_SET);
                $l_d = substr(fgets($handle, 100), 0, -1);
                $ip_a = explode("\t", $l_d);
                $ip_a[3] && $ip_a[2] .= ' '.$ip_a[3];
                fclose($handle);

                return $ip_a[2];
            }
            $ip = $this->_d_ip($d_ip);
            while (!$f && !$l_d && ($wholeIP >= 0)) {
                if (($s = strpos($d, "\n".$wholeIP.'.')) !== false) {
                    $s = $s + $offset;
                    list($l_d, $f) = $this->_s_ip($handle, $s, $ip);
                    if ($f) {
                        return $f;
                    }
                    while ($l_d && preg_match("/^\n$wholeIP/i", "\n".$l_d) !== false) {
                        list($l_d, $f) = $this->_s_ip($handle, $s, $ip, $l_d);
                        if ($f) {
                            return $f;
                        }
                    }
                }
                if (strpos($wholeIP, '.') !== false) {
                    $wholeIP = substr($wholeIP, 0, strrpos(substr($wholeIP, 0, -1), '.'));
                } else {
                    if ($txt == '0.txt') {
                        return $unknowIp;
                    }
                    $wholeIP--;
                }
            }
        }

        return $unknowIp;
    }

    /**
     * 从index.dat查找索引IP.
     *
     * @param $ip_1
     * @param $ip_2
     *
     * @return array
     */
    private function _getIPIndex($ip_1, $ip_2)
    {
        $index = array();
        if ($handle = @fopen($this->_indexFile, 'rb')) {
            $offset = ($ip_1 * $this->_bsize) + ($ip_2 * 4);
            fseek($handle, $offset, SEEK_SET);
            if (!feof($handle)) {
                $c1 = unpack('Nkey', fread($handle, 4));
                $c1 = $c1['key'];
                $c2 = 0;
                while (!feof($handle) && $c2 == 0) {
                    $c2 = unpack('Nkey', fread($handle, 4));
                    $c2 = $c2['key'];
                }
                if ($c1 != 0 && $c2 != 0) {
                    $index = array($c1, $c2);
                }
            }
        } else {
            $index = array(-1, -1);
        }

        return $index;
    }

    /**
     * 从index.dat查找索引IP.
     *
     * @param $db
     * @param $s
     * @param $ip
     * @param $l_d
     *
     * @return unknown_type
     */
    private function _s_ip($db, $s, $ip, $l_d = null)
    {
        if (empty($l_d)) {
            fseek($db, $s, SEEK_SET);
            $l_d = fgets($db, 100);
        }
        $ip_a = explode("\t", $l_d);
        $ip_a[0] = $this->_d_ip(explode('.', $ip_a[0]));
        $ip_a[1] = $this->_d_ip(explode('.', $ip_a[1]));
        if ($ip < $ip_a[0]) {
            $f = $l_d = '';
        } elseif ($ip >= $ip_a[0] && $ip <= $ip_a[1]) {
            fclose($db);
            $ip_a[3] && $ip_a[2] .= ' '.$ip_a[3];
            $f = $ip_a[2];
            $l_d = '';
        } else {
            $f = '';
            $l_d = fgets($db, 100);
        }

        return array($l_d, $f);
    }

    /**
     * @param $d_ip
     *
     * @return string
     */
    private function _d_ip($d_ip)
    {
        $d_ips = '';
        foreach ($d_ip as $value) {
            $d_ips .= '.'.sprintf('%03d', str_replace('*', '255', $value));
        }

        return substr($d_ips, 1);
    }
}
